import math
import time
import threading
import requests
import json
import struct
from pymodbus.client.sync import ModbusSerialClient

class TValue:
    def __init__(self, triggerType = 't0' , triggerThreshold=0):
        self.triggerType = triggerType
        self.triggerThreshold = triggerThreshold
        self.oldValue = 0
        self.oldTm = 0
        self.newValue = 0
        self.newTm = 0
        self.changed = 0
    def setValue(self , v):
        try:
            self.oldTm = self.newTm
            self.oldValue = self.newValue
            self.newTm = time.time()
            self.newValue = v
            changed = 0
            if self.oldTm == 0 :
                changed = 1
            else:
                if self.triggerType=='t1':
                    changed = 0 if self.oldValue==v else 1
                elif self.triggerType == 't2':
                    dlt = abs(self.newValue - self.oldValue)
                    changed = 0 if dlt <= abs(self.triggerThreshold) else 1
                elif self.triggerType == 't3':
                    if self.oldValue == 0 :
                        changed =1
                    else:
                        dlt = abs(self.newValue - self.oldValue)
                        dltRate = dlt*1.0 / abs(self.oldValue)
                        changed = 0 if dltRate <= self.triggerThreshold else 1
            self.changed = changed
        except Exception as er:
            print(er)
    def updateDpByRegs(self , dp , regs):
        try:
            dpType = dp['dataType']
            regAddr = dp['regAddrStart']
            regValue = regs[regAddr]
            v = 0
            if dpType == 'd1': #UINT16
                v = regValue
            elif dpType == 'd2': #INT16
                byteArr = regAddr.to_bytes(2 , byteorder='big' , signed=False)
                v = int.from_bytes(byteArr , byteorder='big' , signed=True)
            elif dpType =='d3' or dpType=='d4': #UINT32 / INT32
                byteArrH = regValue.to_bytes(2 , byteorder='big' , signed=False)
                byteArrL = regs[regAddr+1].to_bytes(2 , byteorder='big' , signed=False)
                byteArr = bytearray( [byteArrH[0],byteArrH[1], byteArrL[0], byteArrL[1]] )
                signFlag = dpType=='d04'
                v = int.from_bytes(byteArr , byteorder='big' , signed= signFlag)
            elif dpType=='d05':
                mast = math.pow(2, dp['bitIndex'])
                v = 0 if (mast & regValue ) == 0 else 1
            elif dpType == 'd06':
                mast = math.pow(2,dp["bitLen"]+1) - 1
                count = dp['bitIndex'] - dp['bitLen']
                mast = mast<<count
                v = 0 if (mast & regValue ) == 0 else 1
            elif dpType == 'd07':
                v = regValue
            elif dpType == 'd08':
                v = regValue - 0x33
            elif dpType == 'd09':
                byteArr = regAddr.to_bytes(2 , byteorder='big' , signed=False)
                v = struct.unpack('>e' , byteArr)
            elif dpType == 'd10':
                byteArrH = regValue.to_bytes(2 , byteorder='big' , signed=False)
                byteArrL = regs[regAddr+1].to_bytes(2 , byteorder='big' , signed=False)
                byteArr = bytearray( [byteArrH[0],byteArrH[1], byteArrL[0], byteArrL[1]] )
                v = struct.unpack('>f' , byteArr)





        except Exception as er:
            print(er)
class TTerminal:
    def __init__(self):
        try:
            self.terminalId = "ziot_mb_ly2024"
            self.initUrl  = "http://hh.hhdata.cn:31297/db/ms/query"
            self.terminalInfo={}
            self.devs={}
            self.channels={}
            self.tasks= {
                "modbusRtu": {}
            }
            self.collections ={
                "modbusRtu":{}
            }
            print('init termilal')
        except Exception as er:
            print(er)
    def loadDatas(self):
        try:
            ps={
                "dbKey_":"iotv10",
                "dbPath_":"iot10/terminal/getTerminalConfig",
                "terId": self.terminalId
            }
            url = self.initUrl
            resp = requests.post(url , ps)
            respConfig = json.loads(resp.text)
            self.terminalInfo = respConfig['result'][0]
            devs =  json.loads(self.terminalInfo['devsJson'])
            for dev in devs :
                dev['dps']=[]
                dev['regs'] = [i for i in range(0xffff)]
                self.devs[dev["devId"]] = dev
            channels = json.loads(self.terminalInfo['channelsJson'])
            for ch in channels:
                ch["tasks"] = {
                    "modbusRtu":[]
                }
                self.channels[ch["chId"]] = ch
            modbusTasks = json.loads(self.terminalInfo['modbusTasksJson'])
            for task in modbusTasks:
                task['modbusRtuDps'] = []
                task['lastTm'] = 0
                self.tasks['modbusRtu'][task['taskId']] = task
                chId = task['channelId']
                if chId in  self.channels.keys():
                    self.channels[chId]['tasks']['modbusRtu'].append(task)
            modbusRtuDps = json.loads(self.terminalInfo['modbusRtuDpsJson'])
            for dp in modbusRtuDps:
                dp['value'] = TValue(dp['triggerType'] , dp['triggerThreshold'])
                #self.dps[dp["configId"]] = dp
                devId = dp['devId']
                if devId in self.devs.keys():
                    self.devs[devId]['dps'].append(dp)
                taskId = dp['taskId']
                if taskId in self.tasks['modbusRtu'].keys():
                    self.tasks['modbusRtu'][taskId]['modbusRtuDps'].append( dp)
            print('load terminal config:')
        except Exception as er:
            print(er)
    def startChannels(self):
        for ch in self.channels.values():
            if ch['protocol']=='modbusRtu':
                if ch['chId'] not in self.collections['modbusRtu'].keys():
                    modbusRtuChannel = TCollector_ModbusRtu(self , ch )
                    self.collections['modbusRtu'][ch['chId']] = modbusRtuChannel
                self.collections['modbusRtu'][ch['chId']].startAsync()

    def updateDevDps(self , dev , dps):
        try:
            for dp in dps:
                dp["value"].updateDpByRegs(dp , dev["regs"])
        except Exception as er:
            print((er))



class TCollector_ModbusRtu:
    def __init__(self , terminal , channelInfo):
        try:
            self.terminal = terminal
            self.channel = channelInfo
            self.thread = None
            self.serialCom = None
            self.dev = self.terminal.devs[channelInfo['devId']]
            self.regs = self.dev['regs']
            self.dps = []
            for task in self.channel['tasks']['modbusRtu']:
                for dp in task['modbusRtuDps']:
                    self.dps.append(dp)

        except Exception as er :
            print(er)
    def start(self):
        try:
            connectFlag = self.connect()
            while connectFlag:
                for task in self.channel['tasks']['modbusRtu']:
                    try:
                        tmNow = time.time()
                        tmTaskLast = task['lastTm']
                        tmCycle = task['collectionCycle']
                        flag = (tmNow*0.001- tmTaskLast*0.001) > tmCycle
                        if flag :
                            task['lastTm'] = tmNow

                            addr = task['regAddrStart']
                            regCount = task['regCount']
                            unitId = task['unitId']
                            response = self.serialCom.read_holding_registers(address=addr, count=regCount, unit=unitId)
                            readFlag = response.isError()
                            if readFlag:
                                print("读取失败：", response)
                            else:
                                print("保持寄存器的值：", response.registers)
                                for i in range(len(response.registers)):
                                    pos = i+ addr
                                    self.regs[pos] = response.registers[i]
                            dps = task['modbusRtuDps']
                            self.terminal.updateDevDps(self.dev , dps)
                    except Exception as eer:
                        print(eer)
        except Exception as er :
            print(er)
    def connect(self):
        res = False
        try:
# 配置串口参数
            COM_PORT = self.channelInfo['comPort']  # 串口号（Windows系统可能是COM1、COM2等）
            BAUD_RATE = self.channelInfo['baudRate']  # 波特率
            PARITY = self.channelInfo['parity']  # 奇偶校验（'N'表示无校验，'E'表示偶校验，'O'表示奇校验）
            STOP_BITS = self.channelInfo['stopBit']  # 停止位
            DATA_BITS = 8  # 数据位
            client = ModbusSerialClient(method='rtu', port=COM_PORT, baudrate=BAUD_RATE,parity=PARITY, stopbits=STOP_BITS, bytesize=DATA_BITS)
            self.serialCom = client
            res = True
        except Exception as er:
            print(er)
        return  res
    def startAsync(self):
        try:
            self.thread= threading.Thread(target= self.start, daemon=True)
            self.thread.start()
        except Exception as er :
            print(er)